package com.roadmap.client.sns;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;

import com.roadmap.client.module.BaseManager;
import com.roadmap.common.BaseConst;
import com.roadmap.common.BaseEntity;
import com.roadmap.common.RdcLogger;
import com.roadmap.entities.Category;
import com.roadmap.entities.Friendship;
import com.roadmap.entities.ext.FriendExt;
import com.roadmap.exception.ActionNotSupportedException;
import com.roadmap.exception.EntityNotFoundException;
import com.roadmap.exception.InvalidActionException;
import com.roadmap.exception.InvalidQueryException;
import com.roadmap.exception.InvalidStatusException;
import com.roadmap.exception.InvalidValueException;
import com.roadmap.exception.NullObjectException;
import com.roadmap.exception.ServerException;
import com.roadmap.repo.db.RdcQuery;

public class FriendshipManager extends BaseManager {
	private final static Logger _log = RdcLogger
			.getLogger(FriendshipManager.class);

	private final static String GROUP_ID_ALL = "0";
	private final static String GROUP_ID_OTHER = "-1";

	public FriendshipManager(String parentId) {
		super(parentId, Friendship.class);
	}

	public FriendshipManager(String ownerId, BaseEntity parent) {
		super(ownerId, Friendship.class, parent);
	}

	/**
	 * This action is not supported for the manager
	 */
	@Override
	public void create(BaseEntity entity) throws NullObjectException,
			InvalidActionException, InvalidValueException, ServerException,
			EntityNotFoundException, ActionNotSupportedException,
			InvalidStatusException {
		throw new ActionNotSupportedException();
	}

	/**
	 * retrieve friends by given group.
	 * 
	 * @param groupId:
	 *            0 - get all friends; -1 - get ungrouped friends
	 */
	public List<FriendExt> getFriendsByGroup(String groupId)
			throws ServerException, NullObjectException, InvalidQueryException,
			InvalidValueException {
		if (groupId == null || groupId.equalsIgnoreCase(GROUP_ID_ALL)) {
			// return all friends
			return getEntitiesOfAspirant();
		}
		else if (groupId.equalsIgnoreCase(GROUP_ID_OTHER)) {
			return getUngroupedFriends();
		}
		else {
			// get the group
			Category grp = (Category) getDbUtil().getEntityById(Category.class,
					groupId);
			Set<String> groupedIDs = grp.getMemberIDSet();
			if (groupedIDs != null && !groupedIDs.isEmpty()) {
				String sql = "select {f.*}, a.realName from (select friend.* from friendship as friend where (friend.friendAId=:uid and friend.friendBId not in(:groups)) or (friend.friendBId=:uid and friend.friendAId not in(:groups))) as friend, aspirant as a where f.friendAId=a.aspirantId or f.friendBId=a.aspirantId order by f.frequency DESC";
				RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
				query.addEntity("f", Friendship.class);
				query.setParameter("uid", getOwnerId());
				query.setParameterList("groups", groupedIDs.toArray());
				query.addScalar("realName", Hibernate.STRING);

				List results = query.getResults();
				if (results != null && !results.isEmpty()) {
					List<FriendExt> friends = new ArrayList<FriendExt>();
					Iterator iter = results.iterator();
					while (iter.hasNext()) {
						Object[] result = (Object[]) iter.next();
						Friendship frd = (Friendship) result[0];
						FriendExt ext = new FriendExt();
						// decide which is the friend id
						String fid = (frd.getId().equals(getOwnerId())) ? frd
								.getFriendId() : frd.getId();
						ext.setFriendId(fid);
						ext.setName((String) result[1]);
						ext.setGroupId(groupId);
						friends.add(ext);
					}

					return friends;
				}
			}
		}
		return null;
	}

	/**
	 * retrieve friends (a list of Aspirant instances) who are ungrouped
	 */
	private List<FriendExt> getUngroupedFriends() throws NullObjectException,
			InvalidQueryException, ServerException, InvalidValueException {
		Set<String> groupedIDs = getGroupedFriendIDs(null);
		String sql = null;
		boolean hasGroups = true;
		if (groupedIDs == null || groupedIDs.isEmpty()) {
			// if no friend is grouped, then get all friends
			sql = "select {f.*}, a.realName from friendship as f, aspirant as a where (f.friendAId=:uid and f.friendBId=a.aspirantId) or ((f.friendBId=:uid and f.friendAId=a.aspirantId)) order by f.frequency DESC";
			hasGroups = false;
		}
		else {
			sql = "select {f.*}, a.realName from (select friend.* from friendship as friend where (friend.friendAId=:uid and friend.friendBId not in(:groups)) or (friend.friendBId=:uid and friend.friendAId not in(:groups))) as friend, aspirant as a where f.friendAId=a.aspirantId or f.friendBId=a.aspirantId order by f.frequency DESC";
		}

		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.addEntity("f", Friendship.class);
		query.setParameter("uid", getOwnerId());
		if (hasGroups) {
			query.setParameterList("groups", groupedIDs.toArray());
		}
		query.addScalar("realName", Hibernate.STRING);

		List results = query.getResults();
		if (results != null && !results.isEmpty()) {
			List<FriendExt> friends = new ArrayList<FriendExt>();
			Iterator iter = results.iterator();
			while (iter.hasNext()) {
				Object[] result = (Object[]) iter.next();
				Friendship frd = (Friendship) result[0];
				FriendExt ext = new FriendExt();
				// decide which is the friend id
				String fid = (frd.getId().equals(getOwnerId())) ? frd
						.getFriendId() : frd.getId();
				ext.setFriendId(fid);
				ext.setName((String) result[1]);
				friends.add(ext);
			}

			return friends;
		}

		return null;
	}

	/**
	 * get all grouped friends.
	 * 
	 * @param groupId:
	 *            if null, query on all groups
	 */
	private Set<String> getGroupedFriendIDs(String groupId)
			throws ServerException {
		// get all the friend groups
		String hql = null;
		Object[] params = null;
		if (groupId == null) {
			// get all groups
			hql = "from Category where ownerId=? and entryType=?";
			params = new Object[] { getOwnerId(),
					Friendship.class.getSimpleName() };
		}
		else {
			// get the special
			hql = "from Category where id=?";
			params = new Object[] { groupId, getOwnerId() };
		}
		List groups = getDbUtil().executeHQLQuery(hql, params);
		if (groups != null && !groups.isEmpty()) {
			Set<String> set = new HashSet<String>();
			Iterator<Category> iter = groups.iterator();
			while (iter.hasNext()) {
				// set.addAll(iter.next().getMemberIDSet());
				String[] ids = iter.next().getMembersArray();
				if (ids != null) {
					for (String id : ids) {
						set.add(id);
					}
				}
			}

			return set;
		}

		return null;
	}

	/**
	 * retrieve all friends of the user returned a list of Object[]{Friendship,
	 * Aspirant};
	 */
	@Override
	public List<FriendExt> getEntitiesOfAspirant() throws NullObjectException,
			InvalidQueryException, ServerException, InvalidValueException {
		return getEntitiesOfAspirant(0);
	}

	/** return the results with fixed number */
	public List<FriendExt> getEntitiesOfAspirant(int num)
			throws NullObjectException, InvalidQueryException, ServerException,
			InvalidValueException {
		String sql = "select {f.*}, a.realName from friendship as f, aspirant as a where (f.friendAId=:uid and f.friendBId=a.aspirantId) or ((f.friendBId=:uid and f.friendAId=a.aspirantId)) order by f.frequency DESC,a.realName ASC";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.addEntity("f", Friendship.class);
		query.addScalar("realName", Hibernate.STRING);
		query.setParameter("uid", getOwnerId());
		if (num > 0) {
			query.setMaxResults(num);
		}

		List results = query.getResults();
		if (results == null || results.isEmpty()) {
			return null;
		}
		List<FriendExt> friends = new ArrayList<FriendExt>();
		Map<String, FriendExt> fgMap = new HashMap<String, FriendExt>();
		Iterator iter = results.iterator();
		while (iter.hasNext()) {
			Object[] result = (Object[]) iter.next();
			Friendship ship = (Friendship) result[0];
			FriendExt friend = new FriendExt(ship);
			// decide which is the friend id
			String fid = (ship.getId().equals(getOwnerId())) ? ship
					.getFriendId() : ship.getId();
			friend.setName((String) result[1]);
			friend.setFriendId(fid);
			friends.add(friend);
			fgMap.put(friend.getFriendId(), friend);
		}

		// find out the group which each friend belongs to
		String hql = "From Category where ownerId=? and entryType=?";
		List groups = getDbUtil()
				.executeHQLQuery(
						hql,
						new Object[] { getOwnerId(),
								Friendship.class.getSimpleName() });
		if (groups != null && !groups.isEmpty()) {
			iter = groups.iterator();
			while (iter.hasNext()) {
				Category group = (Category) iter.next();
				String[] fids = group.getMembersArray();
				if (fids == null) {
					continue;
				}
				for (int i = 0; i < fids.length; i++) {
					FriendExt ext = fgMap.get(fids[i]);
					if (ext != null) {
						ext.setGroupId(fids[i]);
					}
				}
			}
		}

		return friends;
	}

	/** move the friend from a group to another one */
	public void changeFriendGroup(String friendId, String fromGroup,
			String toGroup) {

	}

	/** remove a friend from its group */
	public void removeFriendFromGroup(String friendId, String group) {

	}

	/** remove friends from its group */
	public void removeFriendsFromGroup(String[] friendIDs, String group) {

	}

	/** add a friend to a group */
	public void addFriendToGroup(String friendId, String group) {

	}

	/** add friends to a group */
	public void addFriendsToGroup(String[] friendIDs, String group) {

	}

	/** retrieve the group which the friend belongs to */
	public void getGroupByFriend(String friendId) {

	}

	public List getGroups() throws ServerException {
		String hql = "from Category where ownerId=? and entryType=?";
		return getDbUtil()
				.executeHQLQuery(
						hql,
						new Object[] { getOwnerId(),
								Friendship.class.getSimpleName() });
	}

	@Override
	public void delete(BaseEntity entity) throws NullObjectException,
			InvalidActionException, InvalidValueException, ServerException,
			EntityNotFoundException, ActionNotSupportedException {
		if (!(entity instanceof FriendExt)) {
			InvalidValueException e = new InvalidValueException(
					"the entity is not an instance of FriendExt");
			_log.error(e);
			throw e;
		}

		FriendExt friend = (FriendExt) entity;
		Session session = getDbUtil().getSession();
		String hql = null;
		// check which group the friend belongs to
		Category group = null;
		if (friend.getFriendId() != null) {
			hql = "from Category where ownerId=? and entryType=? and memberIDs like '%"
					+ friend.getFriendId() + "%'";
			try {
				Query q = session.createQuery(hql);
				q.setParameter(0, friend.getId());
				q.setParameter(1, Friendship.class.getSimpleName());
				// a friend can belong to only one group
				group = (Category) q.uniqueResult();
				if (group != null) {
					// delete the friend ID from the group
					String ids = group.getMemberIDs().replace(
							friend.getFriendId()
									+ BaseConst.CATEGORY_ID_SEPARATOR, "");
					group.setMemberIDs(ids);
				}
			}
			catch (HibernateException e) {
				_log.error("failed to execute query.", e);
				throw e;
			}
		}

		// update the data
		Transaction tx = null;
		try {
			// create a query to delete specific friendship
			hql = "delete from Friendship where (friendAId=:aid and friendBId=:bid) or (friendBId=:aid and friendAId=:bid)";
			Query q1 = session.createQuery(hql);
			q1.setParameter("aid", friend.getId());
			q1.setParameter("bid", friend.getFriendId());

			// commit the updates
			tx = session.beginTransaction();
			q1.executeUpdate();
			if (group != null) {
				session.update(group);
			}
			tx.commit();
		}
		catch (HibernateException e) {
			_log.error("failed to delete friendship and update the group", e);
			if (tx != null) {
				tx.rollback();
			}
		}
		finally {
			session.close();
		}
	}

	@Override
	public BaseEntity get(String entityId) throws NullObjectException,
			InvalidQueryException, ServerException, InvalidValueException {
		if (entityId == null) {
			InvalidValueException e = new InvalidValueException(
					"entity id is NULL");
			_log.error(e);
			throw e;
		}
		String hql = "from Friendship friend where (friend.id=? and friend.friendId=?) or (friend.friendId=? and friend.id=?)";
		Object[] params = { this.getOwnerId(), entityId, this.getOwnerId(),
				entityId };
		Object obj = this.getDbUtil().getUniqueEntity(hql, params);
		return (BaseEntity) obj;
	}
}
